#pragma once

#include <stdexcept>
#include <sstream>
#include <vector>

#include "Nodes/Defs.h"
#include "Utils/Compare.h"

//////// Multiple node base class
class MultipleNode;
class MultipleNodeOr;
class MultipleNodeAnd;
class MultipleNodeNot;
class MultipleNodeAtom;

template<>
struct NodeTraits<MultipleNode>
{
    typedef MultipleNodeOr NodeOr;
    typedef MultipleNodeAnd NodeAnd;
    typedef MultipleNodeNot NodeNot;
    typedef MultipleNodeAtom NodeAtom;
};

class MultipleNode
{
protected:
    typedef std::vector<MultipleNode *> VectNodes;

    const NodeType m_eNodeType;
    VectNodes m_vChildren;

protected:
    MultipleNode(const NodeType eNodeType)
        : m_eNodeType(eNodeType)
    {
    }

public:    
    virtual ~MultipleNode()
    {
        for(VectNodes::const_iterator it = m_vChildren.begin(); it != m_vChildren.end(); ++it)
            delete *it;
    }

    static inline std::string getNodeKind()
    {
        return "MultipleNode";
    }
    
    inline NodeType getNodeType() const
    {
        return m_eNodeType;
    }
    
    inline size_t getChildrenCount() const
    {
        return m_vChildren.size();
    }
    
    inline MultipleNode * getChild(size_t idx) const
    {
        return m_vChildren.at(idx);
    }
    
    inline void setChild(size_t idx, MultipleNode * pNode)
    {
        m_vChildren.at(idx) = pNode;
    }
    
    virtual bool compare(MultipleNode * pNode) const
    {
        return compareNodes(this, pNode);
    }
    
    virtual void accept(NodeVisitor<MultipleNode> * pVisitor) = 0;
};

//////// Multiple Or
class MultipleNodeOr : public MultipleNode
{
public:
    MultipleNodeOr()
        : MultipleNode(NT_OR)
    {
    }

    inline void addChild(MultipleNode * pNode)
    {
        m_vChildren.push_back(pNode);
    }
    
    inline void addChild(MultipleNodeOr * pNode)
    {
        m_vChildren.insert(m_vChildren.end(), pNode->m_vChildren.begin(), pNode->m_vChildren.end());
        pNode->m_vChildren.clear();
        delete pNode;
    }
    
    virtual void accept(NodeVisitor<MultipleNode> * pVisitor)
    {
        pVisitor->visitOr(this);
    }
};


//////// Multiple And
class MultipleNodeAnd : public MultipleNode
{
public:
    MultipleNodeAnd()
        : MultipleNode(NT_AND)
    {
    }

    inline void addChild(MultipleNode * pNode)
    {
        m_vChildren.push_back(pNode);
    }

    inline void addChild(MultipleNodeAnd * pNode)
    {
        m_vChildren.insert(m_vChildren.end(), pNode->m_vChildren.begin(), pNode->m_vChildren.end());
        pNode->m_vChildren.clear();
        delete pNode;
    }
    
    virtual void accept(NodeVisitor<MultipleNode> * pVisitor)
    {
        pVisitor->visitAnd(this);
    }
};

//////// Multiple Not
class MultipleNodeNot : public MultipleNode
{
public:
    MultipleNodeNot(MultipleNode * pNode)
        : MultipleNode(NT_NOT)
    {
        m_vChildren.push_back(pNode);
    }

    virtual void accept(NodeVisitor<MultipleNode> * pVisitor)
    {
        pVisitor->visitNot(this);
    }
};

//////// Multiple Atom
class MultipleNodeAtom : public MultipleNode
{
    std::string m_sText;
    
public:
    MultipleNodeAtom(const std::string & sText)
        : MultipleNode(NT_ATOM)
        , m_sText(sText)
    {
    }
    inline std::string getAtomText() const
    {
        return m_sText;
    }

    virtual bool compare(MultipleNode * pNode) const
    {
        if(NT_ATOM != pNode->getNodeType())
            return false;
        
        return (m_sText == static_cast<MultipleNodeAtom*>(pNode)->getAtomText());        
    }

    virtual void accept(NodeVisitor<MultipleNode> * pVisitor)
    {
        pVisitor->visitAtom(this);
    }
};


////////// Factory
template<>
inline MultipleNodeOr * NodeFactory<MultipleNode>::createNodeOr(MultipleNode * pLeftNode, MultipleNode * pRightNode) const
{
    if(NT_OR == pLeftNode->getNodeType())
    {
        if(NT_OR == pRightNode->getNodeType())
            static_cast<MultipleNodeOr*>(pLeftNode)->addChild(static_cast<MultipleNodeOr*>(pRightNode));
        else
            static_cast<MultipleNodeOr*>(pLeftNode)->addChild(pRightNode);
        return static_cast<MultipleNodeOr*>(pLeftNode);
    }

    MultipleNodeOr * pNewNode = new MultipleNodeOr();
    pNewNode->addChild(pLeftNode);
    if(NT_OR == pRightNode->getNodeType())
    {
        pNewNode->addChild(static_cast<MultipleNodeOr*>(pRightNode));
    }
    else
        pNewNode->addChild(pRightNode);
    return pNewNode;
}
    
template<>
inline MultipleNodeAnd * NodeFactory<MultipleNode>::createNodeAnd(MultipleNode * pLeftNode, MultipleNode * pRightNode) const
{
    if(NT_AND == pLeftNode->getNodeType())
    {
        if(NT_AND == pRightNode->getNodeType())
            static_cast<MultipleNodeAnd*>(pLeftNode)->addChild(static_cast<MultipleNodeAnd*>(pRightNode));
        else
            static_cast<MultipleNodeAnd*>(pLeftNode)->addChild(pRightNode);
        return static_cast<MultipleNodeAnd*>(pLeftNode);
    }

    MultipleNodeAnd * pNewNode = new MultipleNodeAnd();
    pNewNode->addChild(pLeftNode);
    if(NT_AND == pRightNode->getNodeType())
        pNewNode->addChild(static_cast<MultipleNodeAnd*>(pRightNode));
    else
        pNewNode->addChild(pRightNode);
    return pNewNode;
}
    
template<>
inline MultipleNodeNot * NodeFactory<MultipleNode>::createNodeNot(MultipleNode * pNode) const
{
    return new MultipleNodeNot(pNode);
}
    
template<>
inline MultipleNodeAtom * NodeFactory<MultipleNode>::createNodeAtom(const std::string & sText) const
{
    return new MultipleNodeAtom(sText);
}
    
template<>
inline std::string NodeFactory<MultipleNode>::getFactoryName() const
{
    return std::string("MultipleNodeFactory");
}







////////////

struct ProcessLeftNode
{
    template<class Node>
    static inline void Create(Node *& pNode)
    {
        pNode = new Node();
    } 
};

struct ProcessRightNode
{
    template<class Node>
    static inline void Create(Node *& pNode)
    {
    } 
};

template<class Node, class CreatePolicy>
class NodeAdderVisitor : public NodeVisitor<MultipleNode>
{
    Node * m_pNode;
    
public:
    Node * getNode() const
    {
        return m_pNode;
    }

    NodeAdderVisitor()
    {
    }

    NodeAdderVisitor(Node * pNode)
        : m_pNode(pNode)
    {
    }

    virtual void visitOr(MultipleNodeOr * pNode)
    {
        CreatePolicy::Create(m_pNode);
        m_pNode->addChild(pNode);
    }

    virtual void visitAnd(MultipleNodeAnd * pNode)
    {
        CreatePolicy::Create(m_pNode);
        m_pNode->addChild(pNode);
    }

    virtual void visitNot(MultipleNodeNot * pNode)
    {
        CreatePolicy::Create(m_pNode);
        m_pNode->addChild(pNode);
    }

    virtual void visitAtom(MultipleNodeAtom * pNode)
    {
        CreatePolicy::Create(m_pNode);
        m_pNode->addChild(pNode);
    }
};

template<>
inline void NodeAdderVisitor<MultipleNodeOr, ProcessLeftNode>::visitOr(MultipleNodeOr * pNode)
{
    m_pNode = pNode;
}

template<>
inline void NodeAdderVisitor<MultipleNodeAnd, ProcessLeftNode>::visitAnd(MultipleNodeAnd * pNode)
{
    m_pNode = pNode;
}

struct VisitorNodeFactory : public NodeFactory<MultipleNode>
{
    inline MultipleNode * createNodeOr(MultipleNode * pLeftNode, MultipleNode * pRightNode) const
    {
        NodeAdderVisitor<MultipleNodeOr, ProcessLeftNode> visitorLeft;
        pLeftNode->accept(&visitorLeft);
        NodeAdderVisitor<MultipleNodeOr, ProcessRightNode> visitorRight(visitorLeft.getNode());
        pRightNode->accept(&visitorRight);
        return visitorRight.getNode();
    }

    inline MultipleNode * createNodeAnd(MultipleNode * pLeftNode, MultipleNode * pRightNode) const
    {
        NodeAdderVisitor<MultipleNodeAnd, ProcessLeftNode> visitorLeft;
        pLeftNode->accept(&visitorLeft);
        NodeAdderVisitor<MultipleNodeAnd, ProcessRightNode> visitorRight(visitorLeft.getNode());
        pRightNode->accept(&visitorRight);
        return visitorRight.getNode();
    }

    inline std::string getFactoryName() const
    {
        return std::string("MultipleNodeFactory+Visitors");
    }
};


